/// <reference path="./Pool.ts" />
namespace RAYTRACING {
    /** 碰撞点信息 */
    class pickInfo {
        public node: Node;
        public point: Vector3 = new Vector3();
        public uv: Vector2 = new Vector2();
        public faceId: number = -1;
        public clone(info: pickInfo) {
            this.node = info.node;
            this.point = info.point;
            this.uv = info.uv;
            this.faceId = info.faceId;
        }
    }

    /**
     * 场景节点
     */
    export class Scene {
        private static readonly rayPool: Pool<Ray> = new Pool<Ray>(Ray, 10);
        private static readonly colorPool: Pool<Color> = new Pool<Color>(Color, 10);
        private static readonly hitInfoPool: Pool<pickInfo> = new Pool<pickInfo>(pickInfo, 10);
        private static readonly vec3Pool: Pool<Vector3> = new Pool<Vector3>(Vector3, 10);

        private static readonly shareHitInfo = new pickInfo();
        private static readonly helpIntInfo = new IntersectionInfo();
        private static readonly helpIntInfo_1 = new IntersectionInfo();
        private static readonly helpRay = new Ray();
        private static readonly helpColor = new Color();

        private _debug : boolean = false;
        private _app: Application;
        private _nodes: Node[] = [];
        private _camera: Camera;
        constructor(_app: Application) {
            //加一个相机
            this._camera = new Camera();
            this._nodes.push(this._camera.node);
            this._app = _app;
        }

        /** 相机 */
        public get camera() { return this._camera; }

        public addNode(_node: Node) {
            this._nodes.push(_node);
        }

        /**
         * 路径追踪 一次
         * @param u U值(x 轴 0 - 1)
         * @param v V值(y 轴 0 - 1)
         * @param outColor 输出颜色
         */
        public pathTraceingOnce(u: number, v: number, outColor: Color) {
            //nodes transfrom
            this.nodesTransfrom();
            //
            this._pathTraceingOnce(u, v, outColor);
        }

        /**
         * 路径追踪 一次
         * @param u U值(x 轴 0 - 1)
         * @param v V值(y 轴 0 - 1)
         * @param outColor 输出颜色
         */
        private _pathTraceingOnce(u: number, v: number, outColor: Color) {
            //outColor 累加

            //根据uv 用相机生成射线
            let ray = this._camera.createRayByUV(u, v);

            // //射线 在场景中查找相交
            // let hitInfo = this.pick(ray);
            // //是否需要继续弹射
            // //采集颜色
            // if (!hitInfo) return;
            // let mat = hitInfo.node.material;
            // if (!mat) return;
            // // outColor.set(1, 0, 0, 1);
            // let _c = Scene.colorPool.newOne();
            // // _c.set(1, 0, 0, 1);
            // // Color.Add(outColor, _c, outColor);
            // Color.Add(outColor, mat.aibedoColor, outColor);
            // Scene.colorPool.deleteOne(_c);

            // let N = hitInfo.node.mesh.transformData.normalBuf[hitInfo.faceId];
            // // let r = N.x < 0 ? 0 : N.x;
            // // let g = N.y < 0 ? 0 : N.y;
            // // let b = N.z < 0 ? 0 : N.z;

            // let r = N.x < 0 ? -N.x : 0;
            // let g = N.y < 0 ? -N.y : 0;
            // let b = N.z < 0 ? -N.z : 0;
            // outColor.set(r, g, b, 1);

            //路径追踪
            let _c = Scene.colorPool.newOne();
            this.rayTraceingColor(ray, _c, 0.8);
            Color.Add(outColor, _c, outColor);
            Scene.colorPool.deleteOne(_c);
        }

        /**
         * 路径追踪
         * @param width 宽度
         * @param height 高度
         * @param sample 采样次数
         * @param cb 结束回调
         */
        public drawPathTraceing(width: number, height: number, sample: number, cb?: Function) {
            let w = width;
            let h = height;
            let unitW = 1 / w;
            let unitH = 1 / h;
            let halfW = unitW / 2;
            let halfH = unitH / 2;
            let frameMaxTime = 1 / 15 * 1000;  //一帧中最大等待时间
            let totalP = w * h;

            //nodes transfrom
            this.nodesTransfrom();

            //make canvas buff
            let canvas: Color[] = [];
            let tempBuf: Uint8Array[] = [];
            for (let i = 0; i < totalP; i++) {
                let c = Scene.colorPool.newOne();
                c.set(0, 0, 0, 0);
                canvas.push(c);
                tempBuf.push(new Uint8Array(3));
            }

            let _color = Scene.colorPool.newOne();
            let _p = this._app.painter;
            let count = totalP;
            let curru = halfW;
            let currv = 1;              //UV 左右系原点是左下 , canvas坐标系是左上 ,处理一下让射线从canvas原点开始。
            let currX = 0;
            let currY = 0;
            let fn = 0;
            let drawFun = () => {
                let time = Date.now();
                while (count > 0) {
                    _color.set(0, 0, 0, 0);
                    //循环
                    let _addu = unitW * Math.random() - halfW;
                    let _addv = unitH * Math.random() - halfH;
                    this._pathTraceingOnce(curru + _addu, currv + _addv, _color);
                    //
                    let pIdx = totalP - count;
                    Color.Add(canvas[pIdx], _color, canvas[pIdx]);

                    //立刻绘制
                    // // Color.toLDR(_color, _color);
                    // let cssColor = _color.toCSS(false);
                    // // console.log(cssColor);
                    // //绘制点
                    // _p.drawPoint(currX, currY, cssColor);

                    //累计uv 偏移
                    curru += unitW;
                    currX++;
                    if (curru > 1) {
                        curru = halfW;
                        currv -= unitH;
                        currX = 0;
                        currY++;
                    }
                    count--;

                    //时钟记时是否一帧中最大等待时间
                    if (Date.now() - time > frameMaxTime) {
                        setTimeout(() => {
                            drawFun();  //下一帧继续循环，执行时间过长，避免卡主线程。
                        }, 0);
                        break; //跳出当前循环
                    }
                }

                if (count <= 0) {
                    fn++;
                    //一帧绘
                    let s = 1 / fn;
                    let _tColor = Scene.colorPool.newOne();
                    let colorBuff = canvas;
                    let enableFilter = false;
                    if(enableFilter){
                        let _tempCanvas : Color[] = [];
                        this.texFilter(width,canvas,_tempCanvas);
                        colorBuff = _tempCanvas;
                    }
                    colorBuff.forEach((c, i) => {
                        Color.Scale(c, s, _tColor);
                        Color.toneMapACES(_tColor, _tColor);
                        let r = _tColor.r * 255;
                        let g = _tColor.g * 255;
                        let b = _tColor.b * 255;
                        let arr = tempBuf[i];
                        arr[0] = r; arr[1] = g; arr[2] = b;
                    });
                    Scene.colorPool.deleteOne(_tColor);

                    _p.drawFrameW(tempBuf, width);
                    console.log(`darwFrame : ${fn}`);

                    //循环
                    if (fn < sample) {
                        //重启下一帧
                        count = totalP;
                        curru = halfW;
                        currv = 1;
                        currX = 0;
                        currY = 0;
                        drawFun();
                    } else {
                        //in pool
                        Scene.colorPool.deleteOne(_color);
                        for (let i = 0; i < totalP; i++) {
                            Scene.colorPool.deleteOne(canvas[i]);
                        }
                        canvas.length = 0;
                        //on end
                        if (cb) cb();
                    }
                }
            }

            //run draw fun
            drawFun();
        }

        /**
         * 射线 在场景中查找相交
         * @param ray 
         * @param useShare 
         * @returns 
         */
        private pick(ray: Ray, useShare = true): pickInfo {
            let info = useShare ? Scene.shareHitInfo : new pickInfo();
            let ishited = this.pickRef(ray, info);
            return ishited ? info : null;
        }

        private pickRef(ray: Ray, out: pickInfo): boolean {
            let iInfo = Scene.helpIntInfo;
            let tempInfo = Scene.helpIntInfo_1;
            let currDis = Number.MAX_VALUE;
            let nodeID = -1;
            //简单暴力遍历
            //遍历 节点
            //遍历 节点mesh所有3角形
            let nLen = this._nodes.length;
            for (let i = 0; i < nLen; i++) {
                let _n = this._nodes[i];
                let _mesh = _n.mesh;
                if (!_mesh) continue;
                // let d = _mesh.data;
                let d = _mesh.transformData;
                let idxBuf = d.indexBuf;
                let triLen = idxBuf.length / 3;
                let posBuf = d.posBuf;
                let normalBuf = d.normalBuf;
                for (let j = 0; j < triLen; j++) {
                    let pIdx = j * 3;
                    let p0 = posBuf[idxBuf[pIdx]];
                    let p1 = posBuf[idxBuf[pIdx + 1]];
                    let p2 = posBuf[idxBuf[pIdx + 2]];
                    //检查与三角形是否相交
                    let hited = ray.intersectsTriangle(p0, p1, p2, tempInfo);
                    if (!hited) continue;
                    //判断法线方向
                    let N = normalBuf[j];
                    let dot = Vector3.Dot(N, ray.direction);
                    if (dot > 0) continue;      //面方向错误
                    // debugger;
                    if (tempInfo.distance < 0.00000000001) continue;  //消除太近的误差
                    //比较是否是最近的
                    if (tempInfo.distance >= currDis) continue;
                    currDis = tempInfo.distance;
                    tempInfo.faceId = j;
                    //当前是较近 三角面
                    iInfo.clone(tempInfo);
                    nodeID = i;
                }
            }

            if (nodeID == -1) return false;
            //整理 pick info
            out.node = this._nodes[nodeID];
            out.faceId = iInfo.faceId;
            out.uv.set(iInfo.bu, iInfo.bv);
            Vector3.ScaleByNum(ray.direction, iInfo.distance, out.point);
            Vector3.Add(out.point, ray.origin, out.point);
            return true;
        }

        /**
         * 计算场景中所有节点的 变换
         */
        private nodesTransfrom() {
            //遍历 所有节点
            let nLen = this._nodes.length;
            for (let i = 0; i < nLen; i++) {
                let _n = this._nodes[i];
                let _mesh = _n.mesh;
                if (!_mesh) continue;
                _mesh.calcTransform(_n.position, _n.rotation, _n.scale);
            }
        }

        /**
         * 计算光线追踪的过程看到了什么颜色
         * @param ray 光线
         * @param out 返回记录的颜色
         * @param pSurvival 反射时存活的概率 0-1
         * @param maxDepth 对大深度(弹射次数)
         */
        private rayTraceingColor(ray: Ray, out: Color, pSurvival = 0.9, maxDepth = 10) {
            out.set(0, 0, 0, 0);
            if (maxDepth <= 0) return;
            let _brightness = Scene.helpColor;
            _brightness.set(1, 1, 1, 1);
            let _ray = Scene.helpRay;
            _ray.copy(ray);
            let dir = _ray.direction;
            for (let i = 0; i < maxDepth; i++) {
                //运气差被干掉退出
                if (Math.random() > pSurvival) { return; };
                //射线检测场景
                // let info = Scene.hitInfoPool.newOne();
                let info = this.pick(_ray);
                //没碰到东西退出
                if (!info) { return; }
                let node = info.node;
                let material = node.material;
                let _type = material.type;
                let c = material.aibedoColor;

                if (_type == MaterialType.Light) {
                    // if(i != 0){
                    //     console.log(i);
                    //     console.log(_ray);
                    //     debugger;
                    // }
                    Color.Mult(_brightness, c, out);
                    break;
                } else {
                    let N = node.mesh.transformData.normalBuf[info.faceId];
                    Color.Mult(_brightness, c, _brightness);
                    //rayScatter
                    _ray.origin.copy(info.point);
                    switch (_type) {
                        case MaterialType.Diffuse:
                            // Vector3.RandomInUnitSphere(dir);
                            Vector3.RandomUnit(dir);
                            Vector3.Add(dir, N, dir);
                            Vector3.Normalize(dir, dir);
                            break;
                        case MaterialType.Specular:
                            Vector3.Reflect(dir, N, dir);
                            break;
                    }

                    //补偿概率剔除的能量损失
                    Color.Scale(_brightness, 1 / pSurvival, _brightness);
                }
            }
        }

        /**
         * 纹理过滤
         * @param src 
         * @param out 
         */
        private texFilter(texW: number, src: Color[], out: Color[]) {
            if (!src || !out) return;
            let queryIdx: number[][] = [];
            let needFillOut = false;
            if (src.length != out.length) {
                out.length = src.length;
                needFillOut = true;
            }

            src.forEach((c, i) => {
                let x = i % texW;
                let y = Math.floor(i / texW);
                let arrY = queryIdx[y];
                if (!arrY) arrY = queryIdx[y] = [];
                arrY[x] = i;
                if (needFillOut) out[i] = new Color();
            });
            let texH = queryIdx.length;
            let temp: Color[] = [];
            temp.length = 25;
            for(let i =0 , len = temp.length;i < len;i++){
                temp[i] = new Color();
            }

            let fun = (x: number, y: number, out: Color) => {
                x = x < 0 ? 0 : x >= texW ? texW -1: x;
                y = y < 0 ? 0 : y >= texH ? texH -1: y;
                let cIdx = queryIdx[y][x];
                out.copy(src[cIdx]);
            }

            let idx = 0;
            let d25 = 1/25;
            let d9 = 1/9;
            for (let y = 0, lenY = texH; y < lenY; y++) {
                for (let x = 0, lenX = texW; x < lenX; x++) {
                    let _color = out[idx];
                    //5x5 卷积
                    fun(-2 + x, 2 + y,  temp[ 0]);
                    fun(-1 + x, 2 + y,  temp[ 1]);
                    fun(0 + x, 2 + y,   temp[ 2]);
                    fun(1 + x, 2 + y,   temp[ 3]);
                    fun(2 + x, 2 + y,   temp[ 4]);
                    fun(-2 + x, 1 + y,  temp[ 5]);
                    fun(-1 + x, 1 + y,  temp[ 6]);
                    fun(0 + x, 1 + y,   temp[ 7]);
                    fun(1 + x, 1 + y,   temp[ 8]);
                    fun(2 + x, 1 + y,   temp[ 9]);
                    fun(-2 + x, 0 + y,  temp[10]);
                    fun(-1 + x, 0 + y,  temp[11]);
                    fun(0 + x, 0 + y,   temp[12]);
                    fun(1 + x, 0 + y,   temp[13]);
                    fun(2 + x, 0 + y,   temp[14]);
                    fun(-2 + x, -1 + y, temp[15]);
                    fun(-1 + x, -1 + y, temp[16]);
                    fun(0 + x, -1 + y,  temp[17]);
                    fun(1 + x, -1 + y,  temp[18]);
                    fun(2 + x, -1 + y,  temp[19]);
                    fun(-2 + x, -2 + y, temp[20]);
                    fun(-1 + x, -2 + y, temp[21]);
                    fun(0 + x, -2 + y,  temp[22]);
                    fun(1 + x, -2 + y,  temp[23]);
                    fun(2 + x, -2 + y,  temp[24]);

                    //add
                    temp.forEach((c,i)=>{Color.Add(_color , c , _color);});
                    Color.Scale(_color , d25 , _color);

                    // //3x3 卷积
                    // let v_0 = temp[6];
                    // let v_1 = temp[7];
                    // let v_2 = temp[8];
                    // let v_3 = temp[11];
                    // let v_4 = temp[12];
                    // let v_5 = temp[13];
                    // let v_6 = temp[16];
                    // let v_7 = temp[17];
                    // let v_8 = temp[18];

                    // Color.Add();

                    //
                    idx++;
                }
            }

        }
    }
}